#include "Bytes.h"

#include <array>
#include <cstring>
#include <sstream>

#include "Logger.h"

namespace zeroplus::mq
{

    static constexpr const char *kHexTable[256] = {
        " 00", " 01", " 02", " 03", " 04", " 05", " 06", " 07", " 08", " 09", " 0A", " 0B", " 0C", " 0D", " 0E", " 0F",
        " 10", " 11", " 12", " 13", " 14", " 15", " 16", " 17", " 18", " 19", " 1A", " 1B", " 1C", " 1D", " 1E", " 1F",
        " 20", " 21", " 22", " 23", " 24", " 25", " 26", " 27", " 28", " 29", " 2A", " 2B", " 2C", " 2D", " 2E", " 2F",
        " 30", " 31", " 32", " 33", " 34", " 35", " 36", " 37", " 38", " 39", " 3A", " 3B", " 3C", " 3D", " 3E", " 3F",
        " 40", " 41", " 42", " 43", " 44", " 45", " 46", " 47", " 48", " 49", " 4A", " 4B", " 4C", " 4D", " 4E", " 4F",
        " 50", " 51", " 52", " 53", " 54", " 55", " 56", " 57", " 58", " 59", " 5A", " 5B", " 5C", " 5D", " 5E", " 5F",
        " 60", " 61", " 62", " 63", " 64", " 65", " 66", " 67", " 68", " 69", " 6A", " 6B", " 6C", " 6D", " 6E", " 6F",
        " 70", " 71", " 72", " 73", " 74", " 75", " 76", " 77", " 78", " 79", " 7A", " 7B", " 7C", " 7D", " 7E", " 7F",
        " 80", " 81", " 82", " 83", " 84", " 85", " 86", " 87", " 88", " 89", " 8A", " 8B", " 8C", " 8D", " 8E", " 8F",
        " 90", " 91", " 92", " 93", " 94", " 95", " 96", " 97", " 98", " 99", " 9A", " 9B", " 9C", " 9D", " 9E", " 9F",
        " A0", " A1", " A2", " A3", " A4", " A5", " A6", " A7", " A8", " A9", " AA", " AB", " AC", " AD", " AE", " AF",
        " B0", " B1", " B2", " B3", " B4", " B5", " B6", " B7", " B8", " B9", " BA", " BB", " BC", " BD", " BE", " BF",
        " C0", " C1", " C2", " C3", " C4", " C5", " C6", " C7", " C8", " C9", " CA", " CB", " CC", " CD", " CE", " CF",
        " D0", " D1", " D2", " D3", " D4", " D5", " D6", " D7", " D8", " D9", " DA", " DB", " DC", " DD", " DE", " DF",
        " E0", " E1", " E2", " E3", " E4", " E5", " E6", " E7", " E8", " E9", " EA", " EB", " EC", " ED", " EE", " EF",
        " F0", " F1", " F2", " F3", " F4", " F5", " F6", " F7", " F8", " F9", " FA", " FB", " FC", " FD", " FE", " FF"};

    // Bytes
    Bytes Bytes::create(size_t size, uint8_t offset) noexcept { return Bytes(kCreate, nullptr, size, offset); }

    Bytes Bytes::shallow_copy(uint8_t *data, size_t size) noexcept { return Bytes(kShallowCopy, data, size, 0); }

    Bytes Bytes::shallow_copy(const uint8_t *data, size_t size) noexcept
    {
        return Bytes(kShallowCopy, const_cast<uint8_t *>(data), size, 0);
    }

    Bytes Bytes::shallow_copy_ptr(void *data) noexcept { return Bytes(kShallowCopy, static_cast<uint8_t *>(data), 0, 0); }

    Bytes Bytes::deep_copy(uint8_t *data, size_t size, uint8_t offset) noexcept
    {
        return Bytes(kDeepCopy, data, size, offset);
    }

    Bytes Bytes::deep_copy(const uint8_t *data, size_t size, uint8_t offset) noexcept
    {
        return Bytes(kDeepCopy, const_cast<uint8_t *>(data), size, offset);
    }

    Bytes Bytes::from_string(const std::string &str, uint8_t offset) noexcept
    {
        if (str.empty())
        {
            return Bytes(kDeepCopy, nullptr, 0, offset);
        }
        return Bytes(kDeepCopy, reinterpret_cast<uint8_t *>(const_cast<char *>(str.data())), str.size(), offset);
    }

    Bytes Bytes::from_user_input(const std::string &str, bool *ok) noexcept
    {
        std::vector<uint8_t> vec;
        std::istringstream ss(str);
        std::string byte_str;
        try
        {
            while (ss >> std::skipws >> byte_str)
            {
                size_t pos = 0;
                int val = std::stoi(byte_str, &pos, 16);
                if (pos != byte_str.length() || val < 0 || val > 0xFF)
                {
                    if (ok)
                    {
                        *ok = false;
                    }
                    return Bytes();
                }
                vec.emplace_back(static_cast<uint8_t>(val & 0xFF));
            }
        }
        catch (std::exception &)
        {
            if (ok)
            {
                *ok = false;
            }
            vec.clear();
            return vec;
        }
        if (ok)
        {
            *ok = true;
        }
        return vec;
    }

    std::string Bytes::convert_to_hex_str(const uint8_t *value, size_t size) noexcept
    {
        if (!value || size == 0)
        {
            return "";
        }
        static constexpr uint8_t kCount = 3;
        std::string str;
        str.reserve(size * kCount);
        str.resize(size * kCount - 1);
        std::memcpy(str.data(), kHexTable[value[0]] + 1, kCount - 1);
        for (size_t i = 1; i < size; ++i)
        {
            std::memcpy(&str[i * kCount - 1], kHexTable[value[i]], kCount);
        }
        return str;
    }

    Bytes::Bytes() noexcept = default;

    Bytes::Bytes(const Bytes &target) noexcept { process_type(kDeepCopy, target.data_, target.size_, target.offset_); }

    Bytes::Bytes(Bytes &&target) noexcept
    {
        if (this == &target)
        {
            return;
        }
        process_type(kMove, target.data_, target.size_, target.offset_, &target);
    }

    Bytes::Bytes(const std::initializer_list<uint8_t> &list) noexcept
    {
        process_type(kCreate, nullptr, list.size(), 0);
        size_t index = 0;
        if (data_)
        {
            for (const auto &value : list)
            {
                data_[index] = value;
                ++index;
            }
        }
    }

    Bytes::Bytes(const std::vector<uint8_t> &data) noexcept
    {
        process_type(kDeepCopy, const_cast<uint8_t *>(data.data()), data.size(), 0);
    }

    Bytes::~Bytes() noexcept
    {
        if (data_ && is_own_ && size_ + offset_ > kStackSize)
        {
            delete[] data_;
        }
    }

    Bytes &Bytes::operator=(const Bytes &target) noexcept
    {
        if (this == &target)
        {
            return *this;
        }
        process_type(kDeepCopy, target.data_, target.size_, target.offset_);
        return *this;
    }

    Bytes &Bytes::operator=(Bytes &&target) noexcept
    {
        if (this == &target)
        {
            return *this;
        }
        process_type(kMove, target.data_, target.size_, target.offset_, &target);
        return *this;
    }

    Bytes &Bytes::operator=(const std::vector<uint8_t> &data) noexcept
    {
        process_type(kDeepCopy, const_cast<uint8_t *>(data.data()), data.size(), 0);
        return *this;
    }

    bool Bytes::operator==(const Bytes &target) const noexcept
    {
        if (size_ != target.size_)
        {
            return false;
        }
        if (data_ == target.data_)
        {
            return true;
        }
        for (size_t i = 0; i < size_; ++i)
        {
            if (data_[i] != target.data_[i])
            {
                return false;
            }
        }
        return true;
    }

    bool Bytes::operator!=(const Bytes &target) const noexcept { return !(*this == target); }

    bool Bytes::operator==(const std::vector<uint8_t> &data) const noexcept
    {
        if (size_ != data.size())
        {
            return false;
        }
        if (data_ == data.data())
        {
            return true;
        }
        for (size_t i = 0; i < size_; ++i)
        {
            if (data_[i] != data[i])
            {
                return false;
            }
        }
        return true;
    }

    bool Bytes::operator!=(const std::vector<uint8_t> &data) const noexcept { return !(*this == data); }

    std::vector<uint8_t> Bytes::to_raw_data() const noexcept
    {
        if (empty() || is_ptr())
        {
            std::vector<uint8_t>();
        }
        return std::vector<uint8_t>(begin(), end());
    }

    std::string Bytes::to_string() const noexcept
    {
        if (empty() || is_ptr())
        {
            std::string();
        }
        return std::string(begin(), end());
    }

    std::string_view Bytes::to_stringView() const noexcept
    {
        return std::string_view(reinterpret_cast<const char *>(data_), size_);
    }

    void Bytes::clear() noexcept
    {
        if (data_ && is_own_ && size_ + offset_ > kStackSize)
        {
            delete[] data_;
        }
        std::memset(stack_data_, 0, kStackSize);
        data_ = nullptr;
        size_ = 0;
        offset_ = 0;
        is_own_ = false;
    }

    void Bytes::shallow_copy(const Bytes &bytes) noexcept { process_type(kShallowCopy, bytes.data_, bytes.size_, 0); }

    void Bytes::deep_copy(const Bytes &bytes) noexcept { process_type(kDeepCopy, bytes.data_, bytes.size_, bytes.offset_); }

    void Bytes::deep_copy_self() noexcept { process_type(kDeepCopy, data_, size_, offset_); }

    Bytes::Bytes(Type type, uint8_t *data, size_t size, uint8_t offset) noexcept { process_type(type, data, size, offset); }

    void Bytes::process_type(Type type, uint8_t *data, size_t size, uint8_t offset, Bytes *tmp) noexcept
    {
        switch (type)
        {
        case kCreate:
        {
            if (size + offset > kStackSize)
            {
                data_ = new (std::nothrow) uint8_t[size + offset]{0};
            }
            else if (size + offset != 0)
            {
                data_ = stack_data_;
            }
            size_ = size;
            offset_ = offset;
            is_own_ = true;
            break;
        }
        case kShallowCopy:
        {
            if (data_ != nullptr && data_ == data)
            {
                LOG_ERROR("Can not shallow copy self.");
                return;
            }
            data_ = data;
            size_ = size;
            offset_ = 0;
            is_own_ = false;
            break;
        }
        case kDeepCopy:
        {
            if (is_own_ && data_)
            {
                if (data_ == data)
                {
                    LOG_ERROR("Can not copy self.");
                    return;
                }
                if (size_ + offset > kStackSize && (size_ != size || offset_ != offset))
                {
                    delete[] data_;
                }
            }
            if (size + offset != 0)
            {
                if (size + offset > kStackSize)
                {
                    if (size_ != size || offset_ != offset)
                    {
                        data_ = new (std::nothrow) uint8_t[size + offset];
                    }
                }
                else
                {
                    data_ = stack_data_;
                }
                if (data && data_)
                {
                    std::memcpy(data_, data, size);
                }
                size_ = size;
                offset_ = offset;
                is_own_ = true;
            }
            else
            {
                data_ = data;
                size_ = 0;
                offset_ = 0;
                is_own_ = false;
            }
            break;
        }
        case kMove:
        {
            if (data_ != nullptr && data_ == data)
            {
                LOG_ERROR("Can not move self.");
                return;
            }
            if (!tmp)
            {
                LOG_ERROR("Reference tmp is empty.");
                break;
            }
            if (is_own_ && data_)
            {
                if (data_ == data)
                {
                    LOG_ERROR("Can not move self.");
                    return;
                }
                if (size_ + offset > kStackSize)
                {
                    delete[] data_;
                }
            }
            if (tmp->size_ + tmp->offset_ != 0 && tmp->size_ + tmp->offset_ <= kStackSize && tmp->is_own_)
            {
                std::memcpy(stack_data_, tmp->stack_data_, kStackSize);
                data_ = stack_data_;
            }
            else
            {
                data_ = tmp->data_;
            }
            size_ = tmp->size_;
            offset_ = tmp->offset_;
            is_own_ = tmp->is_own_;
            tmp->is_own_ = false;
            break;
        }
        default:
            break;
        }
    }

    std::ostream &operator<<(std::ostream &ostream, const Bytes &target) noexcept
    {
        if (target.empty())
        {
            ostream << "{}";
            return ostream;
        }
        if (target.is_ptr())
        {
            ostream << "(ptr)" << static_cast<void *>(target.data_);
            return ostream;
        }
        if (target.offset_ > 0)
        {
            ostream << "(offset=" << +target.offset_ << ") ";
        }
        ostream << Bytes::convert_to_hex_str(target.data_, target.size());
        return ostream;
    }

} // namespace zeroplus::mq
